﻿using System;
using System.Collections.Generic;
using System.Linq;
using RoboContainer.Core;
using TimeMachine.Engine.Analytics;
using TimeMachine.Engine.Analytics.Impl;
using TimeMachine.Engine.Charting;
using TimeMachine.Engine.Metrics;
using TimeMachine.Engine.Storing;
using TimeMachine.Engine.Vcs;

namespace TimeMachine.Engine.Agents
{
	public class PlotAgent
	{
		private readonly IProjectSettings projectSettings;

		public PlotAgent(IProjectSettings projectSettings)
		{
			this.projectSettings = projectSettings;
		}

		public PlotAgentResult GetPlot(string metricName, string author, DateTime start, DateTime end, int strangeRevisionsCount)
		{
			var container = new Container(
				c =>
					{
						c.ForPlugin<IProjectSettings>().UseInstance(projectSettings);
						c.ForPlugin<IPlotSettings>().UseInstance(
							new PlotSettings
								{
									Author = author,
									MetricName = metricName,
									FromDate = start,
									TillDate = end
								});
					});
			var plotter = container.Get<IPlotter>();

			MetricDescriptorAttribute metricDescription = container.GetPluggableTypesFor<IMetric>()
				.Select(p => p.GetMetadata<MetricDescriptorAttribute>())
				.Single(d => d.MetricId == metricName);
			StrangeRevsAnalysisData strangeRevs =
				metricDescription.AnalyseStranges
					? GetStrangeRevsData(plotter, metricName, strangeRevisionsCount, container)
					: null;

			var averager = container.Get<Averager>();
			return new PlotAgentResult(
				plotter.TryGetPlotUrl(averager.Get(plotter.GetPreferedPointsCount())), strangeRevs);
		}

		private static StrangeRevsAnalysisData GetStrangeRevsData(
			IPlotter plotter, string metricName, int strangeRevisionsCount, IContainer container)
		{
			var metricValues = container.Get<IMetricsStorage>();
			var plotSettings = container.Get<IPlotSettings>();
			IEnumerable<CommitMetric> commits =
				container.Get<ICommitsInfoProvider>().Get()
				.Where(c => c.Date >= plotSettings.FromDate && c.Date <= plotSettings.TillDate)
				.Select(
					c => new CommitMetric(c, metricValues.GetValue(metricName, c.Revision)));
			IEnumerable<CommitMetric> strangeValues =
				commits.Where(c => c.Metric.Value != 0.0).OrderBy(c => -c.Metric.Value).Take(strangeRevisionsCount);

			IEnumerable<MetricValue> allValues = metricValues.GetAll(metricName);
			double averageValue = allValues.Any() ? allValues.Average(v => v.Value) : 0.0;
			var distributor = container.Get<DistributionGenerator>();

			PlotData plotData = distributor.Get(commits, plotter.GetPreferedPointsCount());
			plotData.PreferedWidth = 400;
			return new StrangeRevsAnalysisData(
				plotter.TryGetPlotUrl(plotData),
				averageValue, strangeValues.ToArray());
		}
	}

	public class DistributionGenerator
	{
		public PlotData Get(IEnumerable<CommitMetric> commits, int preferedPointsCount)
		{
			IEnumerable<PlotPoints> values = commits
				.OrderBy(c => -c.Metric.Value)
				.LogRepresentatives()
				.Select((pair, i) => new PlotPoints(i, pair.Key.ToString(), pair.Value.Metric.Value));
			return new PlotData(values.ToArray(), new[] {"distribution"});
		}
	}

	public class PlotAgentResult
	{
		public PlotAgentResult(string plotUrl, StrangeRevsAnalysisData strangeRevsAnalysisData)
		{
			PlotUrl = plotUrl;
			StrangeRevsAnalysisData = strangeRevsAnalysisData;
		}

		public string PlotUrl { get; private set; }
		public StrangeRevsAnalysisData StrangeRevsAnalysisData { get; private set; }
	}

	public class StrangeRevsAnalysisData
	{
		public StrangeRevsAnalysisData(string valueDistributionPlotUrl, double average, CommitMetric[] strangeRevs)
		{
			ValueDistributionPlotUrl = valueDistributionPlotUrl;
			Average = average;
			StrangeRevs = strangeRevs;
		}

		public string ValueDistributionPlotUrl { get; private set; }
		public double Average { get; private set; }
		public CommitMetric[] StrangeRevs { get; private set; }
	}
}